using System;
using System.Collections.Generic;
using System.Text;
using SlimDX;
using Spacedash.Logic.Scenario;
using Barrage.Global;
using Barrage;

namespace Spacedash.Logic
{
    /// <summary>
    /// Turning state of a ship. Can have three values (not turning, turning left or turning right)
    /// </summary>
    public enum TurnState
    {
        /// <summary>
        /// Not turning
        /// </summary>
        NotTurning = 0,
        /// <summary>
        /// Turning left
        /// </summary>
        TurningLeft = 1,
        /// <summary>
        /// Turning right
        /// </summary>
        TurningRight = 2
    }

    /// <summary>
    /// State of a ship, based on the race state. Can have three values (stopped, running and finished)
    /// </summary>
    public enum ShipState
    {
        /// <summary>
        /// Stopped: Can't move at all (For example at the start of a race)
        /// </summary>
        Stopped = 0,
        /// <summary>
        /// Running: The ship can turn, accelerate, brake, etc.
        /// </summary>
        Running = 1,
        /// <summary>
        /// Finished: The ship moves forward but the user can't turn, accelerate or brake. Also it won't be affected by cells on the scenario
        /// </summary>
        Finished = 2
    }

    static class ShipTuning
    {
        const float MAX_SPEED = 10;
        const float MIN_SPEED = 8;

        const float MAX_TURN_TIME = 0.275f;
        const float MIN_TURN_TIME = 0.125f;

        const float MAX_ACCEL = 4;
        const float MIN_ACCEL = 2;

        const float MAX_BRAKE = 10;
        const float MIN_BRAKE = 6;

        const float MIN_BOOST_EXTRA = 0.25f;
        const float MAX_BOOST_EXTRA = 0.75f;

        public static float MaxSpeed(Profile p)
        {
            return MIN_SPEED + (MAX_SPEED - MIN_SPEED) * p.Speed;
        }

        public static float Braking(Profile p)
        {
            return MIN_BRAKE + (MAX_BRAKE - MIN_BRAKE) * p.Handling;
        }
        
        public static float Acceleration(Profile p)
        {
            return MIN_ACCEL + (MAX_ACCEL - MIN_ACCEL) * p.Handling;
        }

        public static float TurnTime(Profile p)
        {
            return MIN_TURN_TIME + (MAX_TURN_TIME - MIN_TURN_TIME) * (1 - p.Handling);
        }

        public static float BoostSpeed(Profile p)
        {
            return MIN_BOOST_EXTRA + (MAX_BOOST_EXTRA - MIN_BOOST_EXTRA) * p.Pursuit;
        }
    }

    /// <summary>
    /// This class does all the logic related to ship movement and handling
    /// </summary>
    public class Ship
    {
        const long COLLISION_DURATION = 500;
        const long ACCELERATION_DURATION = 300;
        const long BOOST_DURATION = 250;

        int playerIndex;
        /// <summary>
        /// Player index of this ship. Used to get gamepad states.
        /// </summary>
        public int PlayerIndex { get { return playerIndex; } }
        int turnFrom;
        float turnProgress;
        /// <summary>
        /// When turning, this value tells the progress of the turn until reaching the destination row
        /// </summary>
        public float TurnProgress { get { return turnProgress; } set { turnProgress = Math.Max(0, Math.Min(1, value)); } }

        Profile profile;
        /// <summary>
        /// The player profile associated to this ship
        /// </summary>
        public Profile Profile { get { return profile; } }

        float speed = 0;
        /// <summary>
        /// Gets / Sets this ship's speed
        /// </summary>
        public float Speed { get { return speed; } set { speed = Math.Min(Math.Max(value, 0), RealMaxSpeed); } }

        TurnState turnState = TurnState.NotTurning;
        /// <summary>
        /// Current turn state of the ship
        /// </summary>
        public TurnState TurnState { get { return turnState; } }

        Vector2 scenarioPosition = Vector2.Zero;
        /// <summary>
        /// Returns the position of this ship in the scenario
        /// </summary>
        public Vector2 ScenarioPosition { get { return scenarioPosition; } set { scenarioPosition = value; } }
        
        Scenario.Scenario scenario;

        ShipState state;
        /// <summary>
        /// Gets / Sets the state of this ship
        /// </summary>
        public ShipState State { get { return state; } set { state = value; } }

        float maxSpeed;
        /// <summary>
        /// Returns the theoretical max speed with the boost factor applied
        /// </summary>
        public float RealMaxSpeed { get { return maxSpeed*(1+ShipTuning.BoostSpeed(profile)*boosting); } }
        /// <summary>
        /// Theoretical max speed of this ship (taking into account states like colliding)
        /// </summary>
        public float MaxSpeed { get { return maxSpeed; } }
        /// <summary>
        /// Ship's max speed based on it's profile (without taking into acount states like colliding)
        /// </summary>
        public float ProfileMaxSpeed { get { return ShipTuning.MaxSpeed(profile); } }
        /// <summary>
        /// Boost benefit that this ship can achieve (in percent to its max speed)
        /// </summary>
        public float ProfileBoost { get { return ShipTuning.BoostSpeed(profile); } }

        float shipAcceleration;

        int racePosition=0;
        /// <summary>
        /// Current place in the race
        /// </summary>
        public int RacePosition { get { return racePosition; } set { racePosition = value; } }

        long lapStart;
        /// <summary>
        /// Time elapsed from the start of the current lap
        /// </summary>
        public long CurrentLapTime { get { return Timer.DefaultTimer.TotalTime - lapStart; } }
        /// <summary>
        /// Current lap number (zero-based)
        /// </summary>
        public int CurrentLapNumber { get { return scenario.LapsElapsed(scenarioPosition); } }
        /// <summary>
        /// Current Lap data
        /// </summary>
        public Lap CurrentLap { get { return new Lap(CurrentLapNumber, CurrentLapTime); } }
        List<Lap> laps = new List<Lap>();
        /// <summary>
        /// List of elapsed laps until now
        /// </summary>
        public List<Lap> ElapsedLaps { get { return laps; } }
        /// <summary>
        /// Gets the best lap of the list of elapsed laps
        /// </summary>
        public Lap BestLap
        {
            get
            {
                Lap best=new Lap(0,0);
                foreach (Lap l in laps)
                {
                    if (best.time == 0)
                    {
                        best = l;
                    }
                    else if (l.time < best.time)
                    {
                        best = l;
                    }
                }
                return best;
            }
        }

        Motion motion= new Motion();
        /// <summary>
        /// Ship 3D Motion
        /// </summary>
        public Motion Motion { get { return motion; } }
        
        Vector3 normal = Vector3.UnitY;
        /// <summary>
        /// Normal of the ground at this ship position
        /// </summary>
        public Vector3 Normal { get { return normal; } }

        Vector3 tangent = Vector3.UnitY;
        /// <summary>
        /// Tangent of the ground at this ship position
        /// </summary>
        public Vector3 Tangent { get { return tangent; } }

        /// <summary>
        /// Color of this ship
        /// </summary>
        public Color3 Color { get { return profile.Color; } }

        long lastCollideTime = 0;
        /// <summary>
        /// Returns true if this ship has collided recently (and should be treated as colliding)
        /// </summary>
        public bool Colliding { get { return (Timer.DefaultTimer.TotalTime - lastCollideTime) <= COLLISION_DURATION; } }

        float accelFactor;
        long lastAccelTime = 0;
        /// <summary>
        /// Returns true if this ship is being accelerated by external factors (like boost cells)
        /// </summary>
        public bool Accelerating { get { return (Timer.DefaultTimer.TotalTime - lastAccelTime) <= ACCELERATION_DURATION; } }
        
        long lastBoostTime = 0;
        float boosting = 0;
        /// <summary>
        /// Returns the amount of boost benefit this ship is having (increases while following a trail)
        /// </summary>
        public float Boosting { get { return boosting; } }
        /// <summary>
        /// Returns true if this ship is having benefit for following other ship's trail.
        /// </summary>
        public bool IsBoosting { get { return (Timer.DefaultTimer.TotalTime - lastBoostTime) <= BOOST_DURATION; } }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="scenario">Race scenario</param>
        /// <param name="playerIndex">Player index</param>
        /// <param name="profile">Profile data</param>
        public Ship(Scenario.Scenario scenario,int playerIndex,Profile profile)
        {
            this.scenario = scenario;
            state = ShipState.Stopped;
            this.playerIndex = playerIndex;
            this.profile = profile;
            ResetVariables();
        }

        void ResetVariables()
        {
            maxSpeed = ShipTuning.MaxSpeed(profile);
        }

         void UpdateMovement()
        {
            if (state != ShipState.Running) return;
            Gamepad pad = Gamepad.Instance((SlimDX.XInput.UserIndex)playerIndex);
            Vector2 stickStatus = new Vector2();
            float accelStatus = 0;
            float brakeStatus = 0;
            if (pad != null)
            {
                stickStatus = pad.StickPosition(Stick.LeftStick);
                brakeStatus = pad.TriggerPosition(Trigger.LeftTrigger);
                accelStatus = pad.TriggerPosition(Trigger.RightTrigger);
            }

            if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.UpArrow))
                accelStatus = 1;

            if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.DownArrow))
                brakeStatus = 1;

            if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.LeftArrow))
                stickStatus.X = -1;

            if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.RightArrow))
                stickStatus.X = 1;

            Speed += shipAcceleration * Timer.DefaultTimer.LastIntervalSeconds * accelStatus;
            Speed -= ShipTuning.Braking(profile) * Timer.DefaultTimer.LastIntervalSeconds * brakeStatus;

            if (turnState == TurnState.NotTurning)
            {
                turnFrom = (int)Math.Round(ScenarioPosition.X);
                TurnProgress = 0;
                if (stickStatus.X < -0.5f)
                    if (turnFrom>(-scenario.ColumnNumber/2))
                        turnState = TurnState.TurningLeft;

                if (stickStatus.X > 0.5f)
                    if (turnFrom < (scenario.ColumnNumber / 2))
                        turnState = TurnState.TurningRight;                
            }

            updateTurn();

        }

         void UpdateModifiers()
        {
            shipAcceleration = ShipTuning.Acceleration(profile);
            maxSpeed = ShipTuning.MaxSpeed(profile);
            if (Colliding)
                maxSpeed /= 4f;
            if (Accelerating)
            {
                maxSpeed += maxSpeed * accelFactor;
                shipAcceleration += shipAcceleration * accelFactor;
            }
            // If the speed was greater than the current max speed, limit it.
            Speed = Speed;
        }

        void UpdateBoost()
        {
            boosting = GameMath.Damping(boosting, IsBoosting?1:0, 0.8f, Timer.DefaultTimer.LastIntervalSeconds);
        }

        void UpdateElapsedLaps()
        {
            if (this.CurrentLapNumber > laps.Count)
                NewLap();
        }

        /// <summary>
        /// Updates the ship's logic
        /// </summary>
        public void Update()
        {
            UpdateModifiers();

            UpdateBoost();

            UpdateMovement();
            
            updateVibration();

            UpdateElapsedLaps();

            ScenarioPosition = new Vector2(ScenarioPosition.X, ScenarioPosition.Y + Timer.DefaultTimer.LastIntervalSeconds * speed);

            motion = scenario.getItemMotion(scenarioPosition,out normal, out tangent);

            UpdateElapsedLaps();

//            color = new Color3(0.5f + (float)Math.Sin(Timer.DefaultTimer.TotalTimeSeconds) / 2f, 0.5f + (float)Math.Sin(1.2f * Timer.DefaultTimer.TotalTimeSeconds) / 2f, 0.5f + (float)Math.Sin(0.5f * Timer.DefaultTimer.TotalTimeSeconds) / 2f);
        }

        /// <summary>
        /// Returns the distance between two ships in the race (not real distance, only in the Y axis)
        /// </summary>
        /// <param name="s">Ship to check distance</param>
        /// <returns>A value with the distance in scenario cells</returns>
        public float OffsetToShip(Ship s)
        {
            return s.ScenarioPosition.Y - scenarioPosition.Y;
        }

        /// <summary>
        /// Sets the ship as colliding
        /// </summary>
        public void Collide()
        {
            lastCollideTime = Timer.DefaultTimer.TotalTime;
        }

        /// <summary>
        /// Sets the ship as accelerating
        /// </summary>
        public void Accelerate()
        {
            Accelerate(1);
        }
        
        /// <summary>
        /// Sets the ship as accelerating with the specified boost factor
        /// </summary>
        /// <param name="factor"></param>
        public void Accelerate(float factor)
        {
            lastAccelTime = Timer.DefaultTimer.TotalTime;
            accelFactor = factor;
        }

        /// <summary>
        /// Sets the ship as boosting (for following other ship's trail)
        /// </summary>
        public void Boost()
        {
            lastBoostTime = Timer.DefaultTimer.TotalTime;
        }

        void updateTurn()
        {
            if (turnState == TurnState.NotTurning) return;
            TurnProgress += Timer.DefaultTimer.LastIntervalSeconds / ShipTuning.TurnTime(profile);
            switch (turnState)
            {
                case TurnState.TurningLeft:
                    ScenarioPosition = new Vector2(turnFrom - Interpolation, ScenarioPosition.Y);
                    break;
                case TurnState.TurningRight:
                    ScenarioPosition = new Vector2(turnFrom + Interpolation, ScenarioPosition.Y);
                    break;
            }
            if (turnProgress == 1)
                turnState = TurnState.NotTurning;
        }

        void updateVibration()
        {
            float vibration = Speed / MaxSpeed;
            vibration = vibration * vibration * vibration;
            Gamepad.Instance((SlimDX.XInput.UserIndex)playerIndex).setVibration(vibration * 0.1f, vibration * 0.2f);
        }

         float Interpolation { get { return GameMath.QuadraticInterpolation(TurnProgress); } }

        void NewLap()
        {
            laps.Add(new Lap(laps.Count, Timer.DefaultTimer.TotalTime - lapStart));
            lapStart = Timer.DefaultTimer.TotalTime;
        }

        /// <summary>
        /// Clears the ship's elapsed laps information
        /// </summary>
        public void ClearLaps()
        {
            lapStart = Timer.DefaultTimer.TotalTime;
            laps.Clear();
        }

        
    }
}
